%% Author: vivimice
%% Created: Jan 13, 2012
%% Description: FIFO queue implementation
-module(buffer).

%%
%% Include files
%%

%%
%% Exported Functions
%%
-export([new/0, init/0, loop/2, 
		 add/2, add_list/2, get/1, get_all/1, 
		 length/1, is_empty/1, close/1]).

%%
%% API Functions
%%
new() ->
	spawn_link(?MODULE, init, []).

init() ->
	?MODULE:loop([], []).

loop(ElementList, ObserverList) ->
	receive
		%% add single element to queue 
		{add, [Head|Rest]} ->
			{element_list, NewElementList, observer_list, NewObserverList} =
				append_element(ElementList, ObserverList, [[Head|Rest]]),
			?MODULE:loop(NewElementList, NewObserverList);
		
		{add, Element} ->
			{element_list, NewElementList, observer_list, NewObserverList} =
				append_element(ElementList, ObserverList, Element),
			?MODULE:loop(NewElementList, NewObserverList);
		
		%% get one single element from queue
		%% block while empty
		{get, From} ->
			if
				erlang:length(ElementList) > 0 ->
					[Head | Rest] = ElementList,
					send_get_result(From, Head),
					?MODULE:loop(Rest, ObserverList);
				true ->
					%% no element in queue
					%% add observer into observer queue, notify when data is ready
					?MODULE:loop(ElementList, ObserverList ++ [From]),
					ok
			end;
		
		%% get all elements from queue
		{get_all, From} ->
			From ! {get_all_result, ElementList},
			?MODULE:loop([], ObserverList);
		
		{length, From} ->
			From ! {length_result, erlang:length(ElementList)},
			?MODULE:loop(ElementList, ObserverList);
			
		close ->
			ok
	end.

%% add single element to queue 
add(Queue, Element) ->
	Queue ! {add, Element}.

%% add contents of a list to queue
add_list(_Queue, []) ->
	ok;
add_list(Queue, [Head|Rest]) ->
	Queue ! {add, Head},
	add_list(Queue, Rest).

%% get and remove one element from queue
%% blocked until data is ready
get(Queue) ->
	Queue ! {get, self()},
	receive
		{get_result, Element} ->
			Element
	end.

%% get and remove all elements from queue
%% return [] is queue is empty
get_all(Queue) ->
	Queue ! {get_all, self()},
	receive
		{get_all_result, List} ->
			List
	end.

%% get length of queue
length(Queue) ->
	Queue ! {length, self()},
	receive
		{length_result, Length} ->
			Length
	end.

is_empty(Queue) ->
	?MODULE:length(Queue) == 0.

close(Queue) ->
	Queue ! close.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

send_get_result(To, Element) ->
	To ! {get_result, Element}.

append_element(ElementList, [], Element) ->
	{element_list, ElementList ++ [Element], observer_list, []};
append_element(ElementList, [HeadObserver|RestObservers], Element) ->
	send_get_result(HeadObserver, Element),
	{element_list, ElementList, observer_list, RestObservers}.
